#include "trap.h"
#include "common.h"
#include "x86_asm.h"
#include "x86_trap.h"
#include "keyboard.h"
#include "vfs.h"

static u8 shiftcode[256] = {
    [0x1D] = CTL,
    [0x2A] = SHIFT,
    [0x36] = SHIFT,
    [0x38] = ALT,
    [0x9D] = CTL,
    [0xB8] = ALT
};

static u8 togglecode[256] = {
    [0x3A] = CAPSLOCK,
    [0x45] = NUMLOCK,
    [0x46] = SCROLLLOCK
};

static u8 normalmap[256] = {
    NO,   0x1B, '1',  '2',  '3',  '4',  '5',  '6',  // 0x00
    '7',  '8',  '9',  '0',  '-',  '=',  '\b', '\t',
    'q',  'w',  'e',  'r',  't',  'y',  'u',  'i',  // 0x10
    'o',  'p',  '[',  ']',  '\n', NO,   'a',  's',
    'd',  'f',  'g',  'h',  'j',  'k',  'l',  ';',  // 0x20
    '\'', '`',  NO,   '\\', 'z',  'x',  'c',  'v',
    'b',  'n',  'm',  ',',  '.',  '/',  NO,   '*',  // 0x30
    NO,   ' ',  NO,   NO,   NO,   NO,   NO,   NO,
    NO,   NO,   NO,   NO,   NO,   NO,   NO,   '7',  // 0x40
    '8',  '9',  '-',  '4',  '5',  '6',  '+',  '1',
    '2',  '3',  '0',  '.',  NO,   NO,   NO,   NO,   // 0x50
    [0x9C] = '\n',      // KP_Enter
    [0xB5] = '/',       // KP_Div
    [0xC8] = KEY_UP,    [0xD0] = KEY_DN,
    [0xC9] = KEY_PGUP,  [0xD1] = KEY_PGDN,
    [0xCB] = KEY_LF,    [0xCD] = KEY_RT,
    [0x97] = KEY_HOME,  [0xCF] = KEY_END,
    [0xD2] = KEY_INS,   [0xD3] = KEY_DEL
};

static u8 shiftmap[256] = {
    NO,   033,  '!',  '@',  '#',  '$',  '%',  '^',  // 0x00
    '&',  '*',  '(',  ')',  '_',  '+',  '\b', '\t',
    'Q',  'W',  'E',  'R',  'T',  'Y',  'U',  'I',  // 0x10
    'O',  'P',  '{',  '}',  '\n', NO,   'A',  'S',
    'D',  'F',  'G',  'H',  'J',  'K',  'L',  ':',  // 0x20
    '"',  '~',  NO,   '|',  'Z',  'X',  'C',  'V',
    'B',  'N',  'M',  '<',  '>',  '?',  NO,   '*',  // 0x30
    NO,   ' ',  NO,   NO,   NO,   NO,   NO,   NO,
    NO,   NO,   NO,   NO,   NO,   NO,   NO,   '7',  // 0x40
    '8',  '9',  '-',  '4',  '5',  '6',  '+',  '1',
    '2',  '3',  '0',  '.',  NO,   NO,   NO,   NO,   // 0x50
    [0x9C] = '\n',      // KP_Enter
    [0xB5] = '/',       // KP_Div
    [0xC8] = KEY_UP,    [0xD0] = KEY_DN,
    [0xC9] = KEY_PGUP,  [0xD1] = KEY_PGDN,
    [0xCB] = KEY_LF,    [0xCD] = KEY_RT,
    [0x97] = KEY_HOME,  [0xCF] = KEY_END,
    [0xD2] = KEY_INS,   [0xD3] = KEY_DEL
};

static u8 ctlmap[256] = {
    NO,      NO,      NO,      NO,      NO,      NO,      NO,      NO,
    NO,      NO,      NO,      NO,      NO,      NO,      NO,      NO,
    C('Q'),  C('W'),  C('E'),  C('R'),  C('T'),  C('Y'),  C('U'),  C('I'),
    C('O'),  C('P'),  NO,      NO,      '\r',    NO,      C('A'),  C('S'),
    C('D'),  C('F'),  C('G'),  C('H'),  C('J'),  C('K'),  C('L'),  NO,
    NO,      NO,      NO,      C('\\'), C('Z'),  C('X'),  C('C'),  C('V'),
    C('B'),  C('N'),  C('M'),  NO,      NO,      C('/'),  NO,      NO,
    [0x9C] = '\r',      // KP_Enter
    [0xB5] = C('/'),    // KP_Div
    [0xC8] = KEY_UP,    [0xD0] = KEY_DN,
    [0xC9] = KEY_PGUP,  [0xD1] = KEY_PGDN,
    [0xCB] = KEY_LF,    [0xCD] = KEY_RT,
    [0x97] = KEY_HOME,  [0xCF] = KEY_END,
    [0xD2] = KEY_INS,   [0xD3] = KEY_DEL
};

int
kbdgetc(void)
{
    static uint shift;
    static u8 *charcode[4] = {
        normalmap, shiftmap, ctlmap, ctlmap
    };
    uint st, data, c;

    st = inb(KBSTATP);

    if ((st & KBS_DIB) == 0)
        return -1;

    data = inb(KBDATAP);

    if (data == 0xE0) {
        shift |= E0ESC;
        return 0;
    } else if (data & 0x80) {
        // Key released
        data = (shift & E0ESC ? data : data & 0x7F);
        shift &= ~(shiftcode[data] | E0ESC);
        return 0;
    } else if (shift & E0ESC) {
        // Last character was an E0 escape; or with 0x80
        data |= 0x80;
        shift &= ~E0ESC;
    }

    shift |= shiftcode[data];
    shift ^= togglecode[data];
    c = charcode[shift & (CTL | SHIFT)][data];

    if (shift & CAPSLOCK) {
        if ('a' <= c && c <= 'z')
            c += 'A' - 'a';
        else if ('A' <= c && c <= 'Z')
            c += 'a' - 'A';
    }

    return c;
}

extern void vgaputc(int c);
#define BACKSPACE 0x100
static void kbd_intr(struct trap_frame *frame)
{

    console_intr();

#if 0
    int c;

    while ((c = kbdgetc()) >= 0) {
        switch (c) {
            case '\x8':
                vgaputc(BACKSPACE);
                break;

            case '\0':
                break;

            default:
                vgaputc(c);
        }
    }

#endif
}
void console_putc(int c)
{
    vgaputc(c);
}

int console_getc()
{
    return kbdgetc();
}


int kbd_init()
{
    irq_register(IRQ_KBD, kbd_intr);
    irq_enable(IRQ_KBD);
    return 0;
}
